#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#ifdef NON_OS

#else
#include <semaphore.h>
#endif

#ifdef INPUT_DEVICE_SUPPORT

#include <config.h>
#include <systems.h>
#include <types.h>
#include <event.h>
#include <gobjs.h>
#include <page.h>
#include <linux_list.h>
#include <drivers/input/input.h>
#include <drivers/input/touchscr.h>

extern int tsd;
extern PAGE *current_page;
extern struct input_device *inp_dev;

static u8 event_mask;
static EVENT save_press = { FLAG_PRESS, { -1, -1 }, 0 };
static EVENT save_click = { FLAG_CLICK, { -1, -1 }, 0 };

#define __PRESS   true
#define __RELEASE false
volatile static BOOLEAN dispatched_event = __RELEASE;  /* true: press, false: release */
volatile static GOBJ *last_pressed_gobj = NULL;
volatile static BOOLEAN pressed = false;

static void event_mouse_press(COORD *pos);
static void event_mouse_release(COORD *pos);
static void event_mouse_click(COORD *pos);
static void event_mouse_double_click(COORD *pos);
static void event_mouse_drag(COORD *pos);

void event_arbitrator(void)
{
    static EVENT event;

    if(!inp_dev || !inp_dev->touch_ops || !inp_dev->touch_ops->polling)
        return;
        
    inp_dev->touch_ops->polling(&event);
    
    if(!event.flag)
        return;

    if(event.flag & FLAG_PRESS & event_mask) {
        save_press.pos.x = event.pos.x;
        save_press.pos.y = event.pos.y;
        save_press.timestamp = event.timestamp;
        event_mouse_press(&save_press.pos);
        return;
    }
    
    if(event.flag & FLAG_RELEASE & event_mask) {
        if((event.timestamp - save_press.timestamp <= CLICK_PERIOD) &&
           (save_click.timestamp > 0) &&
           (event.timestamp - save_click.timestamp <= DOUBLE_CLICK_PERIOD) &&
           (FLAG_DBCLICK & event_mask)) {
            /* it's a double click */
            event_mouse_double_click(&save_click.pos);
            event_mouse_release(&save_click.pos);
            /* reset save_click */
            save_click.pos.x = -1;
            save_click.pos.y = -1;
            save_click.timestamp = 0;
        }
        else if((event.timestamp - save_press.timestamp <= CLICK_PERIOD) &&
                (save_press.timestamp > 0) && (FLAG_CLICK & event_mask)) {
            /* it's a click */
            save_click.pos.x = save_press.pos.x;
            save_click.pos.y = save_press.pos.y;
            save_click.timestamp = event.timestamp;
            event_mouse_click(&save_click.pos);
            event_mouse_release(&save_click.pos);
        }
        else {
            /* it's a release */
            save_click.pos.x = -1;
            save_click.pos.y = -1;
            save_click.timestamp = 0;
            event_mouse_release(&event.pos);
        }
            
        /* reset saved press coordinates */
        save_press.pos.x = -1;
        save_press.pos.y = -1;
    }
}

u32 event_dispatcher(COORD *pos)
{
    GOBJ_LINK_NODE *l;
    struct list_head *node;

    if(current_page == NULL) {
        DPRINTF("Current page is not specified\n");
        return 0;
    }
    //DPRINTF("Touch X: %d, Y: %d\n", pos->x, pos->y);
    list_for_each_reverse(node, &current_page->gobj_link_root) {
        l = container_of(node, GOBJ_LINK_NODE, list);
        
        if(l && l->gobj) {
            if(!l->gobj->visible)
                continue;
        }
        else continue;
        
        if((pos->x >= l->sx) && (pos->y >= l->sy) && 
           (pos->x <= l->dx) && (pos->y <= l->dy)) {
            DPRINTF("Dispatched!\n");
            return (u32)l;
        }
    }
    return 0;
}

inline void event_available(u8 available)
{
    event_mask = available;
}

static void event_mouse_press(COORD *pos)
{
    GOBJ_LINK_NODE *l;

    l = (GOBJ_LINK_NODE *)event_dispatcher(pos);
    if(l && l->gobj) {
        if(l->gobj->onPress && l->gobj->activate) {
            /* Check if the continuous PRESS events are acting on the same GOBJ */
            if(last_pressed_gobj && (l->gobj != last_pressed_gobj)) {
                /* The continuous PRESS events are NOT acting on the same GOBJ,
                   so make a RELEASE for previous GOBJ */
                pressed = false;
                dispatched_event = __RELEASE;
                if(last_pressed_gobj->onRelease)
                    last_pressed_gobj->onRelease((void *)last_pressed_gobj);
                last_pressed_gobj = NULL;
                return;
            }
            else if(!last_pressed_gobj && pressed) {
                /* Do nothing when the continuous PRESS events are starting from
                   a position that without containing any GOBJs */
                return;
            }
            else if((l->gobj->event_trigger_type == EVENT_TRIG_SINGLE) &&
                    (l->gobj == last_pressed_gobj)) {
                return;
            }
            dispatched_event = __PRESS;
            last_pressed_gobj = l->gobj;
            if(l->gobj->onPress)
                l->gobj->onPress(l->gobj);
            return;
        }
        else {
            dispatched_event = __PRESS;
            last_pressed_gobj = l->gobj;
            return;
        }
    }
    
    /* No GObj to be triggered */
    pressed = true;
    
    if(dispatched_event) {
        /* Make a release when a continuous PRESS event out of GOBJ region */
        if(last_pressed_gobj) {
            if(last_pressed_gobj->onRelease)
                last_pressed_gobj->onRelease((void *)last_pressed_gobj);
            last_pressed_gobj = NULL;
        }
        dispatched_event = __RELEASE;
    }
}

static void event_mouse_release(COORD *pos)
{
    GOBJ_LINK_NODE *l;
    
    /* Make sure a PRESS has been generated, or just ignore this RELEASE */
    if(!dispatched_event) {
        last_pressed_gobj = NULL;
        pressed = false;
        return;
    }
        
    l = (GOBJ_LINK_NODE *)event_dispatcher(pos);
    if(l && l->gobj) {
        if(l->gobj->onRelease && l->gobj->activate) {
            dispatched_event = __RELEASE;
            l->gobj->onRelease(l->gobj);
        }
    }
    last_pressed_gobj = NULL;
    pressed = false;
}

static void event_mouse_click(COORD *pos)
{
    GOBJ_LINK_NODE *l;
  
    l = (GOBJ_LINK_NODE *)event_dispatcher(pos);
    if(l && l->gobj)
        if(l->gobj->onClick && l->gobj->activate) {
            l->gobj->onClick(l->gobj);
        }
}

static void event_mouse_double_click(COORD *pos)
{
    GOBJ_LINK_NODE *l;
  
    l = (GOBJ_LINK_NODE *)event_dispatcher(pos);
    if(l && l->gobj)
        if(l->gobj->onDoubleClick && l->gobj->activate) {
            l->gobj->onDoubleClick(l->gobj);
        }
}

static void event_mouse_drag(COORD *pos)
{
    GOBJ_LINK_NODE *l;
  
    l = (GOBJ_LINK_NODE *)event_dispatcher(pos);
    if(l && l->gobj)
        if(l->gobj->onDrag && l->gobj->activate) {
            l->gobj->onDrag(l->gobj);
        }
}

#endif /* INPUT_DEVICE_SUPPORT */
